#pragma once

#include "../Shared/GObjHelpers.hpp"
#include "Macro.hpp"

#include <vector>
#include <glibmm.h>
#include <assert.h>
#include <stack>

// C standard:
// http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
// paragraph 6.10

namespace BuildNG
{
  class InStream;
  class OutStream;
}

namespace CPreProc
{
  class Group;
  class GroupPart;
  class IfSection;
  class IfGroup;
  class IfGroupCondition;
  class PreProcessorContext;
  class Macro;
  class TokenizerInput;

  class Group : public BuildNG::Object
  {
  public:
    void process(PreProcessorContext &context) const;

    std::vector<Glib::RefPtr<GroupPart> > group_parts;
  };

  class GroupPart : public BuildNG::Object
  {
  public:
    virtual void process(PreProcessorContext &context) const = 0;
  };

  class IfSection : public GroupPart
  {
  public:
    virtual void process(PreProcessorContext &context) const;

    std::vector<Glib::RefPtr<IfGroup> >     if_groups;
  };

  class IfGroupCondition : public BuildNG::Object
  {
  public:
    virtual bool evaluate(PreProcessorContext &context) = 0;

    virtual void load(BuildNG::InStream &src) = 0;
    virtual void save(BuildNG::OutStream &dest) = 0;
  };

  class IfGroup : public Group
  {
  public:
    /// should be NULL for the else block.
    Glib::RefPtr<IfGroupCondition>          condition;
  };

  class ControlLine : public GroupPart
  {
  public:
  };

  class DefineDirective : public ControlLine
  {
  public:
    static Glib::RefPtr<DefineDirective> parse(Tokenizer &tokenizer,bool directive_read);
    static Glib::RefPtr<DefineDirective> parse(const Glib::ustring &line,bool directive_read);

    virtual void                      process(PreProcessorContext &context) const;

  private:
    DefineDirective(Tokenizer &tokenizer,bool directive_read);

    Glib::RefPtr<Macro>         macro;
  };

  class UndefDirective : public ControlLine
  {
  public:
    static Glib::RefPtr<UndefDirective> parse(Tokenizer &tokenizer,bool directive_read);
    static Glib::RefPtr<UndefDirective> parse(const Glib::ustring &line,bool directive_read);

    virtual void                      process(PreProcessorContext &context) const;

    const Glib::ustring&              get_macro_name() const;
    void                              set_macro_name(const Glib::ustring &name);

  private:
    UndefDirective(Tokenizer &tokenizer,bool directive_read);

    Glib::ustring               macro_name;
  };

  class ErrorDirective : public ControlLine
  {
  public:
    static Glib::RefPtr<ErrorDirective> parse(Tokenizer &tokenizer,bool directive_read);

    ErrorDirective(const Glib::ustring &message);

    virtual void process(PreProcessorContext &context) const;

  private:
    ErrorDirective(Tokenizer &tokenizer,bool directive_read);

    Glib::ustring message;
  };

  class WarningDirective : public ControlLine
  {
  public:
    WarningDirective(const Glib::ustring &message);

    virtual void process(PreProcessorContext &context) const;

    Glib::ustring message;
  };

  class LineDirective : public ControlLine
  {
  public:
    LineDirective(int line);

    virtual void process(PreProcessorContext &context) const;

    int line;
  };

  class TextLine : public GroupPart
  {
  public:
    virtual void process(PreProcessorContext &context) const = 0;
  };

  class PreProcFile : public Group
  {
  public:
    PreProcFile(){}

    static Glib::RefPtr<PreProcFile>    parse(Glib::RefPtr<TokenizerInput> tokenizer_doc);
    static Glib::RefPtr<PreProcFile>    parse(BuildNG::InStream &stream);
    static Glib::RefPtr<PreProcFile>    parse(const Glib::ustring &in_file_name);

    void                                process(PreProcessorContext &context) const;
    
  private:

    Glib::ustring   path;
  };

  enum ErrorSource
  {
    ErrorSource_Directive, 
  };

  enum WarningSource
  {
    WarningSource_Directive,
  };

  enum IncludeSearchType
  {
    IncludeSearchType_AngularBrackets,
    IncludeSearchType_Quoted,
  };

  class PreProcessorContext : public IMacroProvider
  {
  public:
    void            process_group(Glib::RefPtr<Group> group);

    void            define_macro(Glib::RefPtr<Macro> macro);
    void            define_macro(const Glib::ustring &symbol_name);
    void            define_macro(const Glib::ustring &symbol_name,const Glib::ustring &value);
    void            define_macro(const Glib::ustring &symbol_name,const Glib::ustring &value,
                      Glib::ArrayHandle<Glib::ustring> replacement_list);
    
    void            undef_macro(const Glib::ustring &symbol_name);

    bool            is_macro_defined(const Glib::ustring &symbol_name) const;

    virtual void    report_error(ErrorSource error_source,const Glib::ustring &msg) { }
    virtual void    report_warning(WarningSource warning_source,const Glib::ustring &msg) { }
    virtual void    report_line_number(int line) { }

    virtual void    push_current_path(const Glib::ustring &path) { }
    virtual void    pop_current_path() { }

    virtual         Glib::RefPtr<Macro> get_macro(const Glib::ustring &name) const;

    virtual Glib::RefPtr<PreProcFile> load_include_file(const Glib::ustring &file_name,IncludeSearchType search_type) { return Glib::RefPtr<PreProcFile>(NULL); }

  private:
    std::map<Glib::ustring,Glib::RefPtr<Macro> > macros;

    typedef std::map<Glib::ustring,Glib::RefPtr<Macro> >::iterator MacrosIt;
    typedef std::map<Glib::ustring,Glib::RefPtr<Macro> >::const_iterator ConstMacrosIt;
  };
}

